/**
 * Copyright (C) 2013 DaiKit.com - daikit4gxt module (admin@daikit.com)
 *
 *         Project home : http://code.daikit.com/daikit4gxt
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.daikit.daikit4gxt.client.action;

import com.daikit.commons.shared.utils.DkIdGenerator;
import com.daikit.daikit4gxt.client.DkBaseConfiguration;
import com.daikit.daikit4gxt.client.log.DkLogger;


/**
 * The action chain executor
 * 
 * @author tcaselli
 * @version $Revision$ Last modifier: $Author$ Last commit: $Date$
 */
class DkActionChainExecutor
{

	private static final DkLogger LOG = DkLogger.getLog(DkActionChainExecutor.class);

	private static DkActionChainExecutor instance = new DkActionChainExecutor();

	public static DkActionChainExecutor get()
	{
		return instance;
	}

	private DkActionChainExecutor()
	{
		// Nothing done.
	}

	private DkBaseAction<?> runningAction = null;

	private int incrementIndex;
	private int numberOfSteps;
	private int nbStepToIgnore;
	private long chainStartTime;
	private long currentActionStartTime;
	private boolean chainStopped;
	private boolean chainTimeout;
	private boolean chainPaused;
	private String executionId;

	public static enum ExecutionStatus
	{
		/**
		 * The action to be executed is part of an already running chain.
		 */
		RUNNING,
		/**
		 * The action will start effectively after a very short delay corresponding to
		 * {@link DkBaseConfiguration#getActionDelayBeforeRunMilliseconds()}
		 */
		CHAIN_STARTING,
		/**
		 * The action is cancelled because an other one is already running
		 */
		CANCELED,
		/**
		 * A chain action is already running. A new one can't be started. The action is added in a chain action creator
		 * set at the end of currently running chain.
		 */
		POSTPONED,
		/**
		 * The action to be started belongs to another chain execution than the current one. It is not possible to restart
		 * a chain action that has already been run. Please create new actions objects for each executions. Current chain
		 * will be stopped.
		 */
		ERROR_1,
		/**
		 * It is not possible to restart a chain action that has already been run. Please create new actions objects for
		 * each executions. Chain action won't be started.
		 */
		ERROR_2,
		/**
		 * A chain action is already running. A new one can't be started. The action should be added in a chain action
		 * creator set at the end of currently running chain but the last chain action of the currently running chain
		 * already has a chain action creator. Chain action won't be started.
		 */
		ERROR_3
	}

	public ExecutionStatus onActionStarted(final DkBaseAction<?> actionStarted,
			final boolean createChainActionCreatorIfAlreadyRunning)
	{
		ExecutionStatus ret;
		// if the chain is already running
		if (isChainRunning())
		{
			if (actionStarted.getChainActionExecutionId() == null)
			{
				if (createChainActionCreatorIfAlreadyRunning)
				{
					if (runningAction.getLastChainAction().chainActionCreator != null)
					{
						LOG.warn("A chain action is already running. A new one can't be started. The action is be added in a chain action creator set at the end of currently running chain but be carefeful because the last chain action of the currently running chain already has a chain action creator.");
						ret = ExecutionStatus.POSTPONED;
					}
					else
					{
						LOG.warn("A chain action is already running. A new one can't be started. The action is added in a chain action creator at the end of currently running chain.");
						ret = ExecutionStatus.POSTPONED;
					}
				}
				else
				{
					LOG.error("A chain action is already running. A new one can't be started.");
					ret = ExecutionStatus.CANCELED;
				}
			}
			else if (actionStarted.getChainActionExecutionId().equals(executionId))
			{
				ret = ExecutionStatus.RUNNING;
			}
			else
			{
				LOG.error("The action to be started belongs to another chain execution than the current one. It is not possible to restart a chain action that has already been run. Please create new actions objects for each executions. Current chain will be stoped.");
				ret = ExecutionStatus.ERROR_1;
			}
		}
		else if (actionStarted.getChainActionExecutionId() != null)
		{
			LOG.error("It is not possible to restart a chain action that has already been run. Please create new actions objects for each executions. Chain action won't be started.");
			ret = ExecutionStatus.ERROR_2;
		}
		// if it is a new chain to be started
		else
		{
			ret = ExecutionStatus.CHAIN_STARTING;
			chainStartTime = System.currentTimeMillis();
			runningAction = actionStarted;
			executionId = DkIdGenerator.generateRandomId(8);
			numberOfSteps = 0;
			DkBaseAction<?> action = actionStarted;
			while (action != null)
			{
				action.setChainActionExecutionId(executionId);
				numberOfSteps++;
				action = action.getChainAction();
			}
			incrementIndex = 0;
		}

		if (ExecutionStatus.CHAIN_STARTING.equals(ret) || ExecutionStatus.RUNNING.equals(ret))
		{
			incrementIndex++;
			chainStopped = false;
			chainTimeout = false;
			chainPaused = false;

			currentActionStartTime = ExecutionStatus.RUNNING.equals(ret) ? System.currentTimeMillis() : chainStartTime;
		}
		return ret;
	}

	public void endChainExecution()
	{
		DkBaseActionProgressDialog.get().close();
		runningAction = null;
	}

	/**
	 * @return whether a chain of actions is currently running. It is forbidden (and not possible) to run 2 chains in
	 *         parallel so be careful not to start a new chain of actions while already executing one.
	 */
	public boolean isChainRunning()
	{
		return runningAction != null;
	}

	/**
	 * @return the incrementIndex
	 */
	public int getIncrementIndex()
	{
		return incrementIndex;
	}

	/**
	 * @param incrementIndex
	 *           the incrementIndex to set
	 */
	public void setIncrementIndex(final int incrementIndex)
	{
		this.incrementIndex = incrementIndex;
	}

	/**
	 * @return the numberOfSteps
	 */
	public int getNumberOfSteps()
	{
		return numberOfSteps;
	}

	/**
	 * @param numberOfSteps
	 *           the numberOfSteps to set
	 */
	public void setNumberOfSteps(final int numberOfSteps)
	{
		this.numberOfSteps = numberOfSteps;
	}

	/**
	 * @return the nbStepToIgnore
	 */
	public int getNbStepToIgnore()
	{
		return nbStepToIgnore;
	}

	/**
	 * @param nbStepToIgnore
	 *           the nbStepToIgnore to set
	 */
	public void setNbStepToIgnore(final int nbStepToIgnore)
	{
		this.nbStepToIgnore = nbStepToIgnore;
	}

	/**
	 * @return the chainStartTime
	 */
	public long getChainStartTime()
	{
		return chainStartTime;
	}

	/**
	 * @param chainStartTime
	 *           the chainStartTime to set
	 */
	public void setChainStartTime(final long chainStartTime)
	{
		this.chainStartTime = chainStartTime;
	}

	/**
	 * @return the currentActionStartTime
	 */
	public long getCurrentActionStartTime()
	{
		return currentActionStartTime;
	}

	/**
	 * @param currentActionStartTime
	 *           the currentActionStartTime to set
	 */
	public void setCurrentActionStartTime(final long currentActionStartTime)
	{
		this.currentActionStartTime = currentActionStartTime;
	}

	/**
	 * @return the chainStopped
	 */
	public boolean isChainStopped()
	{
		return chainStopped;
	}

	/**
	 * @param chainStopped
	 *           the chainStopped to set
	 */
	public void setChainStopped(final boolean chainStopped)
	{
		this.chainStopped = chainStopped;
	}

	/**
	 * @return the chainTimeout
	 */
	public boolean isChainTimeout()
	{
		return chainTimeout;
	}

	/**
	 * @param chainTimeout
	 *           the chainTimeout to set
	 */
	public void setChainTimeout(final boolean chainTimeout)
	{
		this.chainTimeout = chainTimeout;
	}

	/**
	 * @return the chainPaused
	 */
	public boolean isChainPaused()
	{
		return chainPaused;
	}

	/**
	 * @param chainPaused
	 *           the chainPaused to set
	 */
	public void setChainPaused(final boolean chainPaused)
	{
		this.chainPaused = chainPaused;
	}

	/**
	 * @return the runningAction
	 */
	public DkBaseAction<?> getRunningAction()
	{
		return runningAction;
	}
}
